package com.towncoder.lightmq.broker.utils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * 基于 MMap api 的文件读写工具类
 */
public class MMapUtil {

    private File file;
    private FileChannel fileChannel;
    private MappedByteBuffer mappedByteBuffer;

    /**
     * 文件从指定 offset 开始读
     */
    public byte[] readContent(int readOffset, int size) {
        // 检测容量，防止越界
        int capacity = mappedByteBuffer.capacity();
        if (readOffset < 0 || readOffset + size > capacity) {
            throw new IndexOutOfBoundsException("readOffset or size is out of bounds");
        }
        mappedByteBuffer.position(readOffset);
        byte[] content = new byte[size];
        for (int i = 0; i < content.length; i++) {
            content[i] = mappedByteBuffer.get(readOffset + i);  // 从内存读取
        }
        return content;
    }

    /**
     * 文件写入到 page cache
     */
    public void writeContent(byte[] content) {
        writeContent(content, false);
    }

    /**
     * 文件写入并强制刷盘,从 offset 0 开始写，不追加
     */
    public void writeContent(byte[] content, boolean force) {
        // 默认刷到page cache中，
        mappedByteBuffer.put(content);
        if (force) {
            // 强制刷盘
            mappedByteBuffer.force();
        }
    }

    /**
     * <p>指定 offset 的映射</p>
     * <li>映射的内存体积 = 结束映射的 offset - 开始映射的 offset </li>
     */
    @SuppressWarnings("all")
    public void loadFileInMMap(String filePath, int startOffset, int mappedSize) throws Exception {
        file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException("filePath is " + filePath + " inValid");
        }
        fileChannel = new RandomAccessFile(file, "rw").getChannel();
        mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, startOffset, mappedSize);  // directByteBuffer
    }

    /**
     * 文件映射后的内存释放,不推荐使用
     */
    // public void clear() throws Exception{
    // 在关闭资源时执行以下代码释放内存
    // 不推荐的原因是因为使用了sun包下不稳定的代码
    // Method m = FileChannelImpl.class.getDeclaredMethod("unmap", MappedByteBuffer.class);
    // m.setAccessible(true);
    // m.invoke(FileChannelImpl.class, mappedByteBuffer);
    // }

    /**
     * 文件映射后的内存释放,推荐
     */
    public void clean() {
        if (mappedByteBuffer == null || !mappedByteBuffer.isDirect() || mappedByteBuffer.capacity() == 0)
            return;
        invoke(invoke(viewed(mappedByteBuffer), "cleaner"), "clean");
    }

    private Object invoke(final Object target, final String methodName, final Class<?>... args) {
        return AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                try {
                    Method method = method(target, methodName, args);
                    method.setAccessible(true);
                    return method.invoke(target);
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                }
            }
        });
    }

    private Method method(Object target, String methodName, Class<?>[] args)
            throws NoSuchMethodException {
        try {
            return target.getClass().getMethod(methodName, args);
        } catch (NoSuchMethodException e) {
            return target.getClass().getDeclaredMethod(methodName, args);
        }
    }

    private ByteBuffer viewed(ByteBuffer buffer) {
        String methodName = "viewedBuffer";
        Method[] methods = buffer.getClass().getMethods();
        for (Method method : methods) {
            if (method.getName().equals("attachment")) {
                methodName = "attachment";
                break;
            }
        }

        ByteBuffer viewedBuffer = (ByteBuffer) invoke(buffer, methodName);
        if (viewedBuffer == null)
            return buffer;
        else
            return viewed(viewedBuffer);
    }

    public static void main(String[] args) throws Exception {
        MMapUtil mMapUtil = new MMapUtil();
        // 实际作用是为了等待 arthas连接
        Scanner input = new Scanner(System.in);
        int size = input.nextInt();
        mMapUtil.loadFileInMMap("/Users/libiao/IdeaProjects/lightmq/data/broker/store/order_cancel_topic/00000000", 0, size * 1024 * 1024);
        System.out.println("映射 " + size + "m 内存");
        // 通过 arthas dashboard 观察 mapped 大小
        TimeUnit.SECONDS.sleep(5);

        System.out.println("释放内存");
        mMapUtil.clean();
        TimeUnit.SECONDS.sleep(10000);
    }
}
